<?php
/**
 * Celebrio FileSystem module
 *
 * @copyright  Copyright (c) 2011 Celebrio Software
 * @package    Celebrio
 * @subpackage FileSystem
 */
namespace Celebrio\FileSystem;

use Nette\Object;
use Nette\Web\User;
use Nette\Environment;

use \InvalidArgumentException;
use \InvalidStateException;

use Celebrio\FileSystem\Drivers\Local;

use Celebrio\FileSystem\IVirtualFileSystem;
use Celebrio\FileSystem\DataFile;
use Celebrio\FileSystem\VirtualFile;

use Celebrio\FileSystem\AccessDeniedException;

/**
 * Virtual File System
 *
 * @author pavel
 */
class VirtualFileSystem extends Object implements IVirtualFileSystem {


    /**
     * @var int ID of the user accessing the VFS
     */
    private $user;

    private $vfsEngine;

    private $fsFactory;

    /**
     * Sets the attribute $userId according to the given parameter.
     *
     * @param \Nette\Web\User $userInfo the object containing the user ID
     *        or the ID itself.
     */
    public function __construct(User $user = null) {
        if ($user == null) {
            $user = Environment::getUser();
        }
        
        $this->user = $user;

        $this->fsFactory = new FileSystemDriverFactory();
        $this->vfsEngine = new VfsEngine($this->user);
    }
    
    
/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */
/* ----------------- DATA MANIPULATING FUNCTIONS ---------------------------- */
/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */

    /**
     * @implements IVirtualFileSystem->load, see that method for details
     *
     * @param DataFile $file
     * @param bool $stream
     * @return DataFile
     * @throws Exception
     */
    public function load(DataFile $file, $stream = false) {
        $virtualFile = VirtualFile::createFromData($file);
        $children = $this->vfsEngine->load($virtualFile);
        $loadedFile = DataFile::createFromVirtual($virtualFile, DataFile::MIMETYPE | DataFile::FSTORAGE);
        if ($children === null) {
            $driver = $this->fsFactory->getDriverToTheFile($loadedFile);
            $loadedFile = $driver->load($loadedFile, $stream);
        } else {
            $childData = array();
            foreach($children as $child) {
                $childData[] = DataFile::createFromVirtual($child, GenericFile::ALL);
            }
            $loadedFile->setData($childData);
            
        }
        return $loadedFile;
    }

    public function getFileInfo(DataFile $file) {
        $virtualFile = VirtualFile::createFromData($file, GenericFile::NONE);
        $this->vfsEngine->getFileInfo($virtualFile);
        return DataFile::createFromVirtual($virtualFile, GenericFile::ALL);
    }

    /**
     * Saves the file content to the disk. If the file does not exist
     * and the current user has permissions to create it, it's created.
     * Missing directories in the path are created as well, if necessary.
     * When error occurs, exception is thrown.
     *
     * @param DataFile $file
     *
     * @throws Exception //TODO specify
     */
    public function save(DataFile $file, $rewrite = false) {
        
        $virtualFile = VirtualFile::createFromData($file, 
                VirtualFile::FSTORAGE | VirtualFile::MIMETYPE);
        $this->vfsEngine->save($virtualFile);
        
        $fileToDriver = DataFile::createFromVirtual($virtualFile,  
                VirtualFile::FSTORAGE | VirtualFile::MIMETYPE | GenericFile::OWNER);
        $fileToDriver->setData($file->getData());

        $driver = $this->fsFactory->getDriverToTheFile($fileToDriver);
        $driver->save($fileToDriver);
        
    }


/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */
/* ----------------- FILE MANIPULATING FUNCTIONS ---------------------------- */
/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */


    /**
     * Moves the file or directory (including sub-entries) to another location.
     * In fact, rename is implemented by this method too.
     *
     * @param DataFile $source
     * @param DataFile $target
     * @param boolean $rewrite Specifies whether possibly existing
     *          files in the target location (directory) should be rewritten.
     *
     * @throws Exception when error occurs
     *
     * @todo specify possible exception types
     */
    public function move(DataFile $source, DataFile $target, $rewrite = false) {
        $virtualSource = VirtualFile::createFromData($source, GenericFile::MIMETYPE);
        $virtualTarget = VirtualFile::createFromData($target, GenericFile::FSTORAGE | GenericFile::MIMETYPE);

        $this->vfsEngine->move($virtualSource, $virtualTarget, $rewrite);
        
        //if the target does not have mimetype assigned, let's use that one from source
        if ($virtualTarget->getMimetypeId() === null) {
            $virtualTarget->setMimetypeId($virtualSource->getMimetypeId());
        }
        
        $source = DataFile::createFromVirtual($virtualSource, GenericFile::FSTORAGE | GenericFile::MIMETYPE);
        $target = DataFile::createFromVirtual($virtualTarget, GenericFile::FSTORAGE | GenericFile::MIMETYPE);

        if ($source->getFstorageName() === $target->getFstorageName()) {
            $driver = $this->fsFactory->getDriverToTheFile($target);
            $driver->move($source, $target);
        } else {
            throw new InvalidStateException("Moving between fstorages doesn't work yet.");
        }
    }

    /**
     * Implements IVirtualFileSystem delete(), deletes the given entry from VFS
     * (including all subdirectories and files if the entry is directory).
     *
     * Because the drivers are not supposed to implement recursive delete,
     * every file is deleted separately (of course in the correct order,
     * from bottom to up).
     *
     * @param DataFile $file The file wich is supposed to be deleted.
     */
    public function delete(DataFile $file) {
        $virtualFile = VirtualFile::createFromData($file);

        $driver = $this->fsFactory->getDriverToTheFile($file);
        try {
            $children = $this->vfsEngine->delete($virtualFile);
        } catch (AccessDeniedException $e) {
            // VFS has NOT deleted whole sub-tree, only some files will be
            // deleted. The files which can be deleted can be obtained from
            // $this->vfsEngine->getDeleted() array (done below).
        }
        
        foreach($this->vfsEngine->getDeleted() as $deleted) {
            $driver->delete(DataFile::createFromVirtual($deleted, GenericFile::MIMETYPE));
        }
    }

    /**
     * Implements IVirtualFileSystem copy(), copies entry in the VFS.
     *
     * The copy() method first loads all files under the source
     * (if it's only single file, it loads only 1 entry). Then
     * changes the URL of all that files (not in DB, just here in PHP)
     * and then saves them into DB as new files with new URL.
     *
     * @param DataFile $source
     * @param DataFile $target
     * @param boolean $rewrite Specifies whether possibly existing
     *          files in the target location (directory) should be rewritten.
     *
     * @throws Exception when error occurs
     *
     * @todo specify possible Exception types
     */
    public function copy(DataFile $source, DataFile $target, $rewrite = false) {

        $virtualSource = VirtualFile::createFromData($source, VirtualFile::NONE);
        $virtualTarget = VirtualFile::createFromData($target, VirtualFile::FSTORAGE);

        $filesToCopy = $this->vfsEngine->deepLoad($virtualSource);

        $copiedRoot = $filesToCopy[0];
        if ($copiedRoot->getUrl() !== $virtualSource->getUrl() || $copiedRoot->getName() !== $virtualSource->getName()) {
            throw new InvalidStateException("Deep load failed somehow, the first file does not match");
        }

        $oldChildrenUrlPattern = $virtualSource->getUrl() . $virtualSource->getName();
        $newChildrenUrlPattern = $virtualTarget->getUrl() . $virtualTarget->getName();

        $oldCopiedRoot = clone $copiedRoot;

        $copiedRoot->setUrl($virtualTarget->getUrl());
        $copiedRoot->setName($virtualTarget->getName());

        $fileArrayToCopy = array();
        $fileArrayToCopy[] = array ($oldCopiedRoot, $copiedRoot);

        for ($ii = 1; $ii < count($filesToCopy); $ii ++) {
            $newUrl = substr_replace($filesToCopy[$ii]->getUrl(), $newChildrenUrlPattern, 0, strlen($oldChildrenUrlPattern));
            $oldFile = clone $filesToCopy[$ii];
            $filesToCopy[$ii]->setUrl($newUrl);
            $fileArrayToCopy[] = array($oldFile, $filesToCopy[$ii]);
        }
        
        $this->vfsEngine->paste($filesToCopy, $rewrite);

        $driver = $this->fsFactory->getDriverToTheFile($target);
        foreach($fileArrayToCopy as $sourceAndTarget) {
            $toSave = DataFile::createFromVirtual($sourceAndTarget[1], GenericFile::NONE);
            $source = $sourceAndTarget[0];
            if (!$source->isDirectory()) {
                $dataFile = $driver->load(DataFile::createFromVirtual($source, GenericFile::NONE));
                $toSave->setData($dataFile->getData());
            }
            $driver->save($toSave);
        }
    }


    /* --- PERMISSION MANIPULATING FUNCTIONS --- */

    //permissions changes

    /* --- OWNER CHANGING FUNCTIONS --- */

    //change owner

    
}